5 1* {3 TechnoBBS - Osa 3: Menukieli {3 ---------------------------- Sami Klemola Artikkelin tässä osassa käsittelen TechMenun ja sen käyttämän kielen. Kieli on C:n tyylinen, mutta paljon yksinkertaisempi. Ensiksi kuvailen kielen rakenteen, sitten siirrytään ohjelmointiin ja tehdään muutama komento. Sen jälkeen esitte- len lisää TechnoBBS:n funktioita ja komentoja. Lopuksi tulee valmiita uusia ko- mentoja menukoodilistauksina. Tämä on näillä näkymin viimeinen TechnoBBS:ään liittyvä artikkeli. Jatkossa käsiteltäviä aiheita kuitenkin voisivat olla Tech- Mail ja TechnoBBS:n liittäminen verkkoon. {3Ohjelmat ja niiden ajaminen {3--------------------------- TechMenu on menukooditulkki, joka ajaa tokenisoitua menukoodia. ASCII-muotoinen lähdekoodi on ensin käännettävä ajettavaksi koodiksi. Siihen tarkoitukseen kuu- luu ohjelmistoon TechMenuComp: TechMenuComp Lähdekoodin oikeellisuuden kanssa kannattaa olla tarkkana, koska kun TechMenu- Comp sekoaa, se valittaa aivan jokaisesta merkistä, joka tiedostossa on. Vir- heilmoituksia tulee satoja... Lisäksi virhetilanteessa ohjelma aiheuttaa laitto- mia muistiosoituksia, Enforcer-hittejä ja jumiutuu joillakin tietyillä virheillä ja kaatuileekin! Virheettömän koodin kääntämisessä ei ole onglemia. ASCII-tiedoston pääte on dat ja käännetyn koodin menu. Esimerkiksi Doors-valikko kääntyy näin: TechMenuComp Doors.dat Doors.menu TechMenuComp kääntää Doors.dat:n ja tuottaa sen pohjalta Doors.menun, jonka TechMenu lataa ja suorittaa. TechMenulle annetaan myös noden numero: TechMenu Silloin käynnistetään päävalikko tai jokin muu, se valikko, johon käyttäjän ha- lutaan tulevan sisään: TechMenu 1 Main.menu Valmis menukoodi on nimeltään MainMenu.menu. Minä käytän kuitenkin vain valikon nimeä, koska "Menu" olisi nimessä aivan turha ja epälooginen lisä. Login-koodis- sa suoritettava käsky tuli jo edellisessä osassa, mutta laitan sen tähän vielä uudestaan. Heti login-ohjelmien ajamisen jälkeen suoritetaan: address command "TechMenu "||ln||" BBS:Menu/Main.menu" TechMenun ajo keskeytetään esimerkiksi, kun käyttäjä antaa Goodbye-komennon. Silloin suoritus palaa Rexx-ohjelmaan, ja siinä tulisi seuraavana olla tarvitta- va Goodbye-koodi. Täydellinen login-ohjelma julkaistiin edellisessä osassa. {3Lähdetiedosto {3------------- TechMenuCompille annettava lähdekoodi on tavallista ASCII-tekstiä. Siihen voi- daan sisällyttää kommentteja tavalliseen C-tyyliin. Kommentti alkaa merkeillä "/*" ja päättyy merkkeihin "*/". Kieli on muutenkin hyvin lähellä C:tä. Kielen operaattorit ovat samantapaisia, lauseet päätetään ";"-merkkiin ja ryhmät koo- taan kaarisulkeisiin. TechMenu tuntee kahdeksan toimintoa: Command Määrittelee komennon Execute Suorittaa toimintoja MenuName Asettaa valikon nimen MenuText Määrittelee valikkotekstitiedoston nimen Prompt Määrittelee kehoitteen HotOn Asettaa ohjelman hotkey-tilaan tai sitten ei LoadMenu Lataa toisen valikon Unknown Määrittelee virhemerkkijonon Näistä merkittävin on Command. Se määrittelee purkkiin komennon. Useita näistä ei tarvita kuin kerran tai ei ollenkaan yhdessä menukooditiedostossa. Käyn nyt nämä kaikki läpi yksitellen. Formaatti MenuName Esimerkki MenuName "Extras" MenuName asettaa valikon nimen. Nykyisellään sitä ei käytetä muuhun kuin valikon nimen näyttämiseen TechCon-ikkunassa, joten sillä ei ole juuri käyttöä. Formaatti MenuText Esimerkki MenuText "Extras" MenuText määrittelee sen tiedoston nimen, jonka TechnoBBS lähettää komennolla SendMenu. Päätettä nimeen ei tarvitse laittaa, vaan TechnoBBS kokeilee eri vaih- toehtoja ja katsoo käyttäjän Extensions-määrittelyn. TechnoBBS:n mukana tulevis- sa valikoissa tätä toimintoa ei hyödynnetä. Formaatti Prompt Esimerkki Prompt "\(27)[35m<\(GetTimeLeft())> \(27)[32mExtras: \(27)[0m" Prompt määrittelee kehotteen, jonka TechnoBBS lähettää käyttäjälle aina, kun se on valmis vastaanottamaan komennon. Se voi olla yksinkertainen merkkijono, tai kuten yllä, sisältää funktiokutsuja. Yllä määritelty kehote näyttäisi kutakuin- kin tältä: <30> Extras: Siinä 30 kuvaa jäljellä olevaa aikaa minuutteina. Nuo kaksi osaa tulostuisivat vielä eri väreillä. Määrittelyn ymmärtäminen ei ole tärkeää tässä vaiheessa. Myöhemmin tutustutaan tarkemmin kielen koukeroihin. Erikoistapaus on viestiva- likko, jossa promptissa kannattaa kertoa lisätietoa. Minulla näytetään viestiva- likon promptissa nykyisen viestialueen nimi ja olemassa olevien viestien rajat eli alueen ensimmäisen ja viimeisen viestin numerot. Formaatti Unknown Esimerkki Unknown "\(27)[31mKomentosi \"%s\" on minulle vieras.\(27)[0m" Unknown määrittelee merkkijonon, joka lähetetään käyttäjälle, kun hän on antanut tuntemattoman komennon. Komento on tuntematon, kun sitä ei ole määritelty nykyi- sessä menukoodissa tai missä tahansa ladatussa menukoodissa globaalina komento- na. Unknown hyväksyy samanlaisen määrittelyn kuin Prompt. Merkkijonossa oleva %s korvataan komennon nimellä, ja tässä se tulostuu lainausmerkkien sisään, jotka on escapoitava slashilla, jotta tulkki ei luulisi quoten olevan merkkijonon ter- minaattori. Formaatti HotOn Esimerkki HotOn "HOTKEYS" HotOn määrittelee UserMisc-muuttujan, jonka arvo määrää, mennäänkö Hotkey-moo- diin vai ei. Jos määritellyn muuttujan arvo on nolla, toimitaan, kuin HotOn-toi- mintoa ei olisi määriteltykään. Muussa tapauksessa siirrytään Hotkey-moodiin, jossa komentojen nimien kirjoittamisen sijaan ne valitaan yksittäisillä näppäimenpainalluksilla. Tällöin luonnollisesti kaikkien käytettäväksi tarkoi- tettujen komentojen on erottava ensimmäisestä merkistään, joka on Hotkey-moodis- sa se koodi, joka laukaisee komennon. Formaatti LoadMenu Esimerkki LoadMenu "Extras.menu" LoadMenu määrittelee ladattavan menukoodin. Normaalisti TechMenu ajetaan vain kerran, jolloin sen käsketään ladata päävalikko. Päävalikossa on erinäinen määrä LoadMenu-toimintoja, jotka lataavat kaikki muut valikot. Tällöin voidaan helpos- ti siirtyä valikosta toiseen menu()-funktiolla. TechMenun sisäiset funktiot käsitellään yksityiskohtaisesti myöhemmin. Kussakin valikossa voi olla myös glo- baaleja komentoja, joiden ajaminen onnistuu mistä tahansa valikosta, kun kaikki valikot on ladattu. Formaatti Execute { } Execute suorittaa kaarisulkuihin sisällytetyt toimenpiteet. Tämä toiminto ei ole ollenkaan käyttökelpoinen, koska sen ollessa käytössä kaikki muut toiminnot jätetään huomiotta. Executea käytettäessä tuloksena ei olekaan valikko vaan sk- ripti, joten toiminnossa ei ole mitään järkeä. Parempi vaihtoehto olisi ollut, että nämä toimenpiteet suoritetaan valikkoon siirryttäessä ja sen jälkeen aloi- tetaan normaali toiminta, mutta sen sijaan koodin suoritus päättyy. Järjetön toiminto! Formaatti Command "" [] { } [""]; Command on TechMenun tärkein toiminto. Sillä määritellään purkkiin komento. Ko- mennon nimi on ensimmäisessä merkkijonossa, ja se on merkitty yllä id:ksi. Tämän jälkeen tulevat optiot, jotka määräävät, kenellä on oikeus käyttää komentoa. Seuraavaksi tulevat komennossa suoritettavat toimenpiteet kaarisuluissa eli var- sinainen komennon koodi. Toimenpiteet käydään läpi seuraavassa luvussa. Viimei- senä voidaan vielä määritellä komennolle nimi, joka lähetetään käyttäjälle Hot- key-moodissa, kun se on tunnistettu. Command-toiminnon optiot voivat olla standalone-optioita tai niihin voi liittyä parametri. Optioilla rajoitetaan komennon käyttöä: A: Asettaa minimioikeustason komennon käyttäjälle G: Määrittelee komennon globaaliksi M: Asettaa maskin komennon käyttäjälle N: Määrittelee tarpeellisten merkkien määrän O: Tarjoaa mahdollisuuden monimutkaiseen kontrolliin Optiot A ja M asettavat käyttäjältä vaadittavat oikeusmäärittelyt. Voidakseen käyttää komentoa käyttäjän oikeustason tulee olla vähintään yhtä suuri kuin A- optiossa annettu luku. Lisäksi M-optiossa annetun luvun päällä olevien bittien tulee olla päällä myös käyttäjän maskissa. Kolmas käyttörajoituksiin liittyvä optio on O. Sitä seuraa kokonainen lause, jonka pitää olla tosi (arvo muu kuin nolla), jotta komentoa voi käyttää. Optio G määrittelee komennon globaaliksi. Globaali komento voidaan suorittaa missä tahansa valikossa. Optio N määrittelee, kuinka monta merkkiä komennon ni- mestä tarvitsee täsmätä, että katsotaan komento annetuksi. Tämä luku kannattaa laittaa aika pieneksi, jotta komennot voidaan lyhentää reilusti. Lyhyillä komen- noilla optiota ei kannata käyttää ollenkaan, vaan komennot kannattaa edellyttää kokonaisina. {3Toimenpiteet {3------------ Komennossa suoritettavat toimenpiteet ovat lähinnä muuttujan asettaminen, suori- tuksen ohjaus tai funktiokutsu. Muuttujat menukielessä alkavat aina dollarimer- killä. Suorituksen ohjaukseen ovat olemassa funktiontyyliset if() ja while() sekä quit ja break. Funktiokutsu voi olla TechMenun sisäinen tai ulkoinen Tech- Con:n tai TechnoBBS:n funktio. TechnoBBS:n funktioita menukoodista kutsuttaessa tulee huomata, ettei noden numeroa tarvita. TechMenu hoitaa sen automaattisesti. Muuttuja asetetaan tutusti: $ = Tässä id on muuttujan nimi ja expression lause, joka tulkitaan ja jonka evaluaa- tion tulos asetetaan muuttujaan. Lauseiden rakenne käydään läpi yksityiskohtai- sesti myöhemmin. Koodin suoritusta ohjaavista toimenpiteistä if() on useimmin käytetty ja hyödyllisin. Sen avulla voidaan koodin suoritusta ehdollistaa: if() tai if() { } Ylempää tapaa voi käyttää silloin, kun ehdollistettavia toimenpiteitä on vain yksi. Kun niitä on useampia, ne erotetaan ryhmäksi kaarisuluilla. Myös yk- sittäinen toimenpide voidaan laittaa kaarisulkeiden sisään. Ehdollistettu koodi, if():n perässä olevat toimenpiteet, suoritetaan silloin, kun if():lle annettu lause on tosi. Jos on tarpeen suorittaa tietyt toimenpiteet, kun se ei ole tosi, lauseen voi invertoida. Lisäksi on mahdollista käyttää else-rakennetta: else tai else { } Tällöin else-rakenne tulee heti if():n perään, ja sen sisältämät toimenpiteet suoritetaan silloin, kun lause ei ole tosi. If() ja else ovat erilliset raken- teelliset osat, eikä kaarisulkeita tarvitse käyttää kummassakin, vaikka toisessa ne olisivatkin. Toinen koodin suoritusta ohjaava toimenpide on while(): while() tai while() { } While() suorittaa perässä tulevia toimenpiteitä niin kauan kuin sille annettu lause on tosi. Jos se ei ole tosi ensimmäisellä while():n suorituskerralla, ei perässä tulevia toimenpiteitä suoriteta ollenkaan. While()-looppi voidaan kes- keyttää break:lla. Mikäli break ei ole while()-toimenpidesarjassa, se keskeyttää koko komennon suorituksen. On mahdollista tehdä looppi, josta ei poistuta ollen- kaan, paitsi break:n avulla: while(1) { /* suoritettavat toimenpiteet */ } Neljäs koodin suoritusta ohjaava toimenpide on quit. Se keskeyttää suorituksen, ja quit:n kohdatessaan TechMenu palaa sen ajaneeseen ohjelmaan. {3Sisäiset funktiot {3----------------- TechMenu tarjoaa neljä funktiota, jotka eivät palauta arvoa, vaan ne toimivat enemmänkin komentoina ja suoritetaan toimintoina: menu() Vaihtaa valikkoa dos() Suorittaa DOS-komennon rexx() Suorittaa Rexx-komennon rxport() Vaihtaa Rexx-porttia Jo aikaisemmin tuli ilmi mahdollisuus ladata useita valikoita yhtaikaa. Kun va- likko on LoadMenu-toiminnolla ladattu, voidaan siihen vaihtaa funktiolla menu(). Parametriksi funktiolle annetaan käännetyn menukoodin tiedostonimi ilman hake- mistopolkua. Esimerkki: menu("Extras.menu"); Funktio dos() suorittaa DOS-komennon. Yleensä sille annetaan suoraan merkkijono, mutta sille voidaan antaa myös funktiokutsu tai niitä voi olla osana merkkijo- noa. Parametrinä annetun lauseen evaluaation tulos ajetaan DOS-komentona. Esimerkki: dos("MakeFList"); Rexx-ohjelmalle annetaan yleensä parametrinä, kuten tunnettua, noden numero, jonka avulla ne kommunikoivat suoraan TechnoBBS-prosessin kanssa. Ulkoinen oh- jelma ei kuitenkaan välttämättä osaa käyttää TechnoBBS:n Rexx-liittymää, vaan kommunikoi standardien I/O-väylien kautta. Avuksi tulee TechIO, jonka avulla saadaan ohjelman stdin- ja stdout-kanavat ohjattua modeemille. Esimerkiksi tie- dostolistaus lähetettäisiin näin: dos("List BBS:Menu <>TECHIO:\(node())"); TechIO:n täytyy tietää, minkä noden kanssa ohjelman tulee kommunikoida. Tieto saadaan sisäisellä funktiolla node(). Näin mitä tahansa ohjelmaa voidaan helpos- ti käyttää linjalta, kunhan se ei avaa omia ikkunoita tai pyytimiä. Funktio rexx() suorittaa Rexx-komennon. Se toimii samaan tapaan kuin dos(). Ko- mento lähetetään aktiiviseen Rexx-porttiin, jonka voi vaihtaa funktiolla rx- port(). Se vastaa toiminnaltaan melko lailla Rexx-kielen Address-komentoa. Esimerkki: rexx("SendModem Hello, world!"); Esimerkki: rxport("TECHCON"); Yleensä Rexx-portti, johon menukoodista lähetetyt komennot menevät, on kyseisen noden TechnoBBS-prosessin portti. Näin esimerkiksi yllä oleva SendModem-komento menisi oikeaan osoitteeseen ja toimisi samoin kuin Rexx-koodista. Kaikille funk- tioille annettavat parametrit ovat lauseita, ja funktiolle varsinaisesti annet- tavat argumentit ovat niiden evaluaation tuloksia. TechMenun varsinaiset sisäiset funktiot ovat: arg() Palauttaa komennon argumentteja node() Palauttaa noden numeron str() Ottaa osan merkkijonosta len() Palauttaa merkkijonon pituuden upper() Muuttaa merkkijonon isokirjaimiseksi lower() Muuttaa merkkijonon pienikirjaimiseksi split() Etsii merkkijonosta toista merkkijonoa Komennon nimen saa selville kutsumalla arg(0):aa. Muut arvot palauttavat varsi- naisia argumentteja, yksi ensimmäisen, kaksi toisen jne. TechnoBBS ei osaa käsi- tellä lainausmerkkejä komentorivillä, joten monisanaiset argumentit tulevat erillisinä. Esimerkiksi komennolle parametrinä annettava käyttäjän nimi tulee aina vähintään kahtena argumenttina. Näiden kanssa tulee olla tarkkana. Kuten jo tulikin ilmi, node():lla saa selville, millä nodella koodia ajetaan. Funktio str() vastaa BASICin MID$()-funktiota. Esimerkiksi str("tapuli", 2,3) antaisi ulos merkkijonon "apu". Toinen argumentti siis kertoo, mistä kohtaa ale- taan ottaa merkkijonoa. Arvo 1 tarkoittaa merkkijonon ensimmäistä merkkiä. Vii- meinen parametri kertoo, kuinka monta merkkiä merkkijonosta otetaan. Funktio len() palauttaa merkkijonon pituuden. Upper() ja lower() muuttavat kaikki kir- jaimet isoiksi ja pieniksi. Funktio split() palauttaa kohdan, jossa merkkijono alkaa toisessa: split("hirsikasa","sika") Tämä lause saisi arvon 4. Merkkijono "sika" löytyy ensimmäisestä merkkijonosta alkaen merkistä 4. Jälleen arvo 1 tarkoittaa merkkijonon ensimmäistä kirjainta. Itse asiassa näiden lauseiden ei tarvitse olla merkkijonoja. Periaatteessa Tech- Menu käsittelee kaikkia lauseita merkkijonoina, ja siksi sen toiminta on joissa- kin tilanteissa vähintään omituista. {3Lauseet {3------- Lauseet koostuvat kahdentyyppisistä elementeistä, operaattoreista ja arvoista. Operaattorit ovat samantyylisiä kuin C:ssä ja niitä on kasapäin. Ne käsitellään yksitellen myöhemmin. Lauseessa voi olla yksi tai useampi elementti. Arvoja on oltava vähintään yksi, mutta niitä voi olla useita, jolloin ne erotetaan toisis- taan operaattoreilla, jotka suorittavat niiden välillä jonkin operaation. Lopul- ta koko lauseen arvo saadaan selville, kun kaikki yksittäiset arvot on selvitet- ty ja operaatiot ratkaistu. Arvot voivat olla seuraavia: $ muuttuja "" merkkijono numero ([]) funktiokutsu Muuttujan nimi, esimerkiksi $muuttuja, tuo sen kohdalle lauseessa muuttujan ar- von. Merkkijono ja numero ovat kiinteitä arvoja, jotka ovat samat joka kerta kun lausetta ratkaistaan. Funktiokutsu tuo sen kohdalle lauseeseen funktion palaut- taman arvon. Lisäksi lauseessa voi olla kolmella tavalla lisää lauseita: ~ käänteistys ! looginen käänteistys () sulkeistus Normaalisti suoraan, muuttujasta tai funktiolta saatu arvo tulee lauseeseen sel- laisenaan, mutta laittamalla "~"- tai "!"-merkki lauseen eteen sen arvo voidaan invertoida. Sulkeiden avulla lauseiden suoritusjärjestystä voidaan muuttaa. Nor- maalisti lauseet tulkitaan vasemmalta oikealle, eikä laskusäännöistä ole tietoa- kaan. Lause ei siis oikeastaan olekaan vain yksi lause, vaan siinä voi olla vaikka kuinka monta lausetta, jotka on kaikki ratkaistava, ennen kuin sen arvo on selvillä. Menukoodissa hyväksyttävät operaattorit ovat: == yhtäsuuruus: arvo on 1, jos operaattorin kahta puolta olevat arvot ovat samat != erisuuruus: arvo on 0, jos ne ovat samat, muuten 1 > suurempi kuin: arvo on 1, jos arvo vasemmalla puolella on suurempi < pienempi kuin: arvo on 1, jos arvo vasemmalla puolella on pienempi >= suurempi tai yhtä suuri kuin: arvo on 1, jos arvo vasemmalla puolella on suurempi tai sama kuin oikealla puolella <= pienempi tai yhtä suuri kuin: arvo on 1, jos arvo vasemmalla puolella on pienempi tai sama kuin oikealla puolella + yhteenlasku: arvo on operaattorin kahta puolta olevien arvojen summa - vähennyslasku: arvo on vasemmalla puolella oleva arvo vähennettynä oikealla puolella olevalla arvolla * kertolasku: arvo on operaattorin kahta puolta olevien arvojen tulo / jakolasku: arvo on vasemmalla puolella oleva arvo jaettuna oikealla puolella olevalla luvulla ++ merkkijonoyhteenlasku: yhdistää operaattorin kahta puolta olevat merkkijonot | tai-toiminto operaattorin kahta puolta olevilla arvoilla || looginen tai-toiminto & ja-toiminto && looginen ja-toiminto ^ ehdoton tai -toiminto Merkkijonoa on syytä vielä käsitellä tarkemmin. Siinä käytetään kenoviivaa esca- pe-merkkinä. Esimerkiksi lainausmerkki saadaan merkkijonoon, kun sitä edeltää kenoviiva. Muuten se tulkitaan merkkijonon päätösmerkiksi. Näin myös kenoviiva itse on escapoitava. Kenoviivaa voivat seurata sulkeet, jolloin merkkijonoon tu- lee sulkeissa olevaa arvoa vastaava ASCII-merkki. Se voi olla myös merkkijono - jotkin funktiot palauttavat merkkijonon. Rivinvaihtoon on käytettävä CRLF:ää eli tässä tapauksessa "\(13)\(10)", mikä on hieman hankalaa. Värejä voi tulostuksessa käyttää ESCAPE-koodin avulla, kuten normaalistikin ANSI:n mukaan. ESCAPE-koodin saa merkkijonoon laittamalla sinne "\(27)". Sulkeiden sisällä voi olla myös kokonainen lause, jonka arvo sisällytetään merk- kijonoon. Lause voidaan suorittaa käynnistysvaiheessa tai joka kerta uudelleen. Mikäli se halutaan suoritettavaksi vain kerran, sitä tulee edeltää avainsana "PARSE". Lauseen arvo voi olla myös merkkijono, jolloin se liittyy varsinaiseen merkkijonoon siihen kohtaan, jossa lause on. {3Ohjelmointi menukielellä {3------------------------ Menukielellä voi tehdä miltei samat asiat kuin Rexx-ohjelmassa. Jotkin asiat ovat helpommin tehtävissä, jotkin vaikeammin ja jotkut ovat jopa mahdottomia. Menukieli sopii lyhyiden koodien tekemiseen ja yksinkertaisten komentojen imple- mentointiin. Vähänkään monimutkaisemmat toiminnot kannattaa tehdä Rexx-kielellä. Rexx-ohjelman voi helposti ajaa menukoodista dos()-funktiolla. Rexx() ei ole tähän tehtävään oikea funktio, koska se lähettää Rexx-komennon ohjelmalle, eikä aja mitään ohjelmaa. Komentojen nimeämiselle ja lyhennyksille kannattaa omaksua jokin tyyli, joka toistuu läpi valikoiden. Komentojen nimet voivat esimerkiksi olla yksittäisiä verbejä tai sitten aina tekemistä ja sen kohdetta kuvaava predikaatin ja objek- tin yhdistelmä, jonka lyhenne tulee aina kummastakin sanasta. Useimmiten komennot kannattaa tehdä tiettyyn valikkoon. Hirveää määrää globaale- ja komentoja tulee välttää. Usein komennot liittyvät esimerkiksi viesteihin tai tiedostoihin, joten niiden sijoitusvalikko on selvä. Itselläni on joukko yleisiä komentoja, jotka eivät liity mihinkään erityisesti, joten tein niille oman Ext- ras-valikon. Minulla on komentoja esimerkiksi koneen muistin ja prosessilistan tutkailuun sekä ajan kyselyyn ja ohjelmiston versionumeron tiedusteluun. Lähde- koodeja näihin on viimeisessä luvussa. Voi myös olla, että teet jonkin erityisen palvelun purkkiisi. Tällöin on kannat- tavaa tehdä sille oma valikko. Minulla on Saku-valikko, jossa voi lukea linjalla aina uusimman Sakun artikkeleita sekä imeä niitä yksittäin. Tällaisia toimintoja varten on yleensä syytä tehdä oma valikko. Ihan muutamaa komentoa varten ei niin kuitenkaan kannata tehdä. Tässä vaiheessa on jo syytä antaa vähän muutakin kuin irrallisia esimerkkejä. Tässä on esimerkki oman valikon käynnistämiseen: Command "extras" (N:1) { rexx("LogEntry Extras"); menu("Extras.menu"); }; Tämä on yksinkertainen komento, jossa vaihdetaan valikkoa. Komennon ajamiseen riitää ensimmäinen merkki, E. Tässä on ensimmäinen kunnollinen komennon lähde- koodi tuosta Extras-valikosta: Command "lc" { rexx("LogEntry Viewing last callers"); rexx("SendASCII Text/LastCallers.txt"); rexx("SendModem \(13)\(10)"); }; Janne Sirenin LastCallers-ohjelma, jonka käyttämisen kuvasin artikkelin edelli- sessä osassa, ylläpitää listaa viimeisistä soittajista tiedostossa Text/LastCal- lers.txt. Komennolla lc käyttäjä voi nyt katsoa edelliset soittajat myös purkis- sa erittäin yksinkertaisella koodilla. Kaikkien komentojen on syytä lopuksi lähettää rivinvaihto eli CRLF, koodit 13 ja 10. Käyttäjästä voisi olla mukavaa saada jotenkin apua hankalampien komentojen käyttämisessä. Yksi mahdollisuus on tehdä Help-komento, jolle annetaan komennon nimi, josta halutaan apua. Tällainen tosin kannattaisi tehdä Rexx-kielellä. Oh- jelma voisi käyttää kullekin valikolle vaikkapa helppitiedostoja, joista se tu- lostaisi halutun komennon osuuden rivipohjaisesti. Toinen tapa on antaa lyhyesti tietoa, kun komennolle annetaan parametriksi kysymysmerkki, vastaavasti kuin shellissä. Itselläni jokainen komento antaa kysymysmerkillä formaattinsa, mutta apu voi olla muutakin: Command "jokainenkomento" { if(arg(1) == "?") { rexx("SendModem \(13)\(10)"); break; } ... }; Järjestelmän toiminnasta kertovia komentoja saa helposti aikaiseksi useita. Tässä on yksi: Command "sys" { rexx("LogEntry Showing system information"); dos("CPU >TECHIO:\(node())"); }; Käyttäjälle ajetaan komento CPU, jonka tuloste ohjataan TechIO:n avulla modee- mille. Tuloste voi olla esimerkiksi: System: 68030 68882 FastROM (INST: Cache Burst) (DATA: Cache NoBurst) Moni käyttäjä ei ehkä ole tällaisesta tiedosta kiinnostunut, mutta heitä kuiten- kin on. Eri asia on, haluaako tästä tehtävän lokimerkintää. Minulla tehdään var- sin monipuoliset lokimerkinnät, mutta joku muu voi haluta vähemmän merkintöjä. Minulla ei käyttäjän tarvitse kuin tulla sisään ja katsella ympärilleen kymmeni- sen minuuttia, niin loki kasvaa kilokaupalla... Komento VER kertoo ohjelmiston versionumeroita: Command "version" (N:3 G:) { rexx("LogEntry Inquiring software version"); dos("Version >TECHIO:\(node())"); dos("Version BBS:Bin/TechCon >TECHIO:\(node())"); dos("Version BBS:Bin/StarTech >TECHIO:\(node())"); }; Tuota viimeistä ei tietenkään kannata ajaa, jos käynnistää nodensa jollakin muulla ohjelmalla. Tällöin komennon voi korvata asianmukaisella komennolla, esi- merkiksi "Version Bin:TrapDoor", jolloin komennon tuloste voisi olla: Kickstart 40.9, Workbench 40.6 TechCon 0.93 TrapDoor 4.279 Ehkei tuo olekaan hyvä idea. Tietääkseni TrapDoorini versio on 1.83 - jostakin syystä se kuitenkin kertoo Version-komennolle ihan muuta. Valikkoon on muistet- tava laittaa mahdollisuus palata päävalikkoon: Command "quit" { Menu("Main.menu"); }; {3Lisää TechnoBBS-komentoja ja -funktioita {3---------------------------------------- Tässä luvussa esittelen tukun tehokkaita TechnoBBS:n komentoja ja funktioita. Yksityiskohtaisessa käsittelyssä ovat komennot ListFiles ja SelectFiles. Seuraa- vassa luvussa on valmiita menukoodeja uusien komentojen aikaansaamiseksi. Niissä hyödynnetään tässä luvussa esiteltäviä komentoja ja funktioita. ListFiles on erittäin tehokas tiedostojenlistauskomento. Sillä voi listata kaik- ki, tietyn ajankohdan jälkeen upitut tai kuvaukseen osuvat tiedostot kaikilta tai tietyltä alueelta tai yhdeltä alueelta ja kaikilta sen ala-alueilta joko erikseen tai yhdessä vanhimmasta uusimpaan, uusimmasta vanhimpaan, aakkosjärjes- tyksessä tai käänteisessä aakkosjärjestyksessä. Tässä ovat komennon optiot: A Kaikki tiedostot kaikilta alueilta E Kaikki tiedostot tällä alueella ja sen ala-alueilla C Jatkuva listaus, ei promptia B Ei ala-alueita I Ei hakemistoja, kaikkien alueiden tiedostot samassa listassa N Ei tyhjiä hakemistoja M Näytä tiedostojen numerot L Näytä pitkät kuvaukset G Kokonaiset aluenimet (todelliset hakemistopolut) Näiden optioiden lisäksi on viisi optiota, joille annetaan myös parametri: O P F D S Komennon tulosteen voi formatoida haluamallaan tavalla. Optioon O liitetyssä formatointimerkkijonossa voi käyttää seuraavia määrittelyjä: %n tiedoston nimi %s tiedoston pituus %c kommentti %d päiväys %t aika %a päivä %p protection-bitit Tiedostoja voi myös etsiä komennon avulla. Tiedostoja voi listata määritte- lemällä patternin (optio P), johon niitä verrataan. Voi myös antaa merkkijonon, jota etsitään tiedoston nimestä ja kuvauksesta (ei kuitenkaan pitkästä kuvauk- sesta) ja listataan ne, joista se löytyi (optio F). Optiolla D voit antaa päiväyksen, josta lähtien tiedostot listataan. Päiväys voi olla joko standardis- sa dd-mmm-yy -muodossa tai päivien määrä tammikuun ensimmäisestä päivästä vuonna 1978 (TechnoBBS:n yleinen päivämääräformaatti). Optiolla S (sort) voit valita, miten tiedostot listataan: N Uusimmasta vanhimpaan O Vanhimmasta uusimpaan A Aakkosjärjestyksessä (suositeltavin tapa) Z Käänteisessä aakkosjärjestyksessä Seuraavassa luvussa on esimerkki ListFiles-komennon implementoinnista purkkiin. Annan suoraan koko lähdekoodin omassa purkissani olevaan List-komentoon. Sillä voi myös käynnistää kokoruututiedostolistaimen, joka aktivoituu komennolla Se- lectFiles. Myös se on hyvin tehokas tiedostojenlistauskomento ja pystyy samaan kuin ListFiles, mutta se on interaktiivinen, ja sen avulla voi seikkailla alueelta toiselle ja myös imeä tiedostoja ja merkitä niitä. SelectFilesin optiot poikkeavat hieman ListFilesin optioista: A Kaikki tiedostot, kaikki ala-alueet E Kaikki tiedostot tällä alueella ja sen ala-alueilla H Näytä nykyisen tiedoston nimi vahvennetulla (katso alla) C Näytä ala-alueet ja salli alueen vaihto (korvaa E:n) Kokoruututiedostonvalitsin toimii niin, että se näyttää ruudullisen tiedostoja, ja listauksessa voi liikkua nuolinäppäimillä. Normaalisti kursorin paikka il- maistaan ">"-merkillä vasemmassa reunassa, mutta sen sijaan voidaan käyttää ni- men vahventamista (optio H), mikä ei kuitenkaan ole suotavaa. Optiot A ja E näyttävät tiedostot samassa listauksessa. Paras vaihtoehto on optio C, joka näyttää tiedostot alueittain ja antaa mahdollisuuden vaihtaa aluetta. Normaalien Download- ja Upload-komentojen lisäksi TechnoBBS:ssä on mahdollisuus siirtää tiedostoja yhtä aikaa kumpaankin suuntaan. Sitä varten on olemassa ko- mento DlAndReceive. Sillä voi imeä ja uppia yhtä aikaa. Ensin imettävät tiedos- tot tulee merkitä ja sen jälkeen vaihtaa alueelle, jolle uppii. Komennon käyttäminen edellyttää kaksisuuntaista protokollaa. Sellainen on ainakin DModem, joka on myös yksisuuntaisena nopeampi kuin ZModem. Jostakin syystä vain harvat purkit tukevat sitä. Esimerkki DModemin alustusmerkkijonoksi on "XY,LY,IY,B2048". Lisäksi tiedostoja voi siirtää funktioilla SendFiles() ja ReceiveFiles(). Recei- veFiles():iä käytetään esimerkiksi vastaanotettaessa replypakettia. SendFiles() taas on käytössä tekstikirjastosovelluksessani, jonka lähdekoodi on seuraavassa luvussa. Nodella on lähetettävistä tiedostoista lista, jonka voi tyhjentää ko- mennolla ClearFiles. Tämän jälkeen lähetettävät tiedostot lisätään listaan yksi- tellen komennolla AddFile. Lopuksi ne lähetetään kutsumalla SendFiles():iä, joka palauttaa nollan, jos siirto epäonnistui. Viestipuolella on monia kiinnostavia komentoja. Yksi niistä on ReWriteMsg. Sen avulla voidaan toteuttaa BBBS:n dup-komentoa vastaava komento. ReWriteMsg lataa määritellyn viestin editoriin, jolloin sitä voidaan muutella ja tallentaa siitä uusi versio. Tämä uusi versio jää voimaan ja vanha tuhotaan. Viesti voi- daan tuhota komennolla KillMsg. Viestejä ei kuitenkaan koskaan varsinaisesti poisteta viestikannasta, koska se olisi liian hidasta, vaan ne ainoastaan mer- kitään kuolleiksi. Ihmettelen kyllä, miksei sitten ole olemassa komentoa UnkillMsg, koska viestin herättäminen kuolleista olisi erittäin yksinkertaista. Olen kuullut, että vies- tejä tapetaan helposti vahingossa, joten tällainen olisi tarpeen. ReWriteMsg edellyttää käyttäjältä viestineditointioikeuksia, jos viesti ei ole hänen it- sensä kirjoittama. KillMsg ei tee tarkistuksia, joten on syytä itse varoa anta- masta kaikkien käyttää sitä. ConvertMsg konvertoi viestikannassa olevan viestin annetusta mer- kistöstä ISO:ksi. Tämä on kätevä toiminto, jos esimerkiksi käyttäjä on uppinut replynsä väärällä merkistöllä. Tällöin tosin inbound-konversiossa merkit ovat saattaneet kääntyä väärin. TechnoBBS tarjoaa erittäin hyvät merkistökonversio- toiminnot. Hankalinta onkin saada käyttäjät valitsemaan oikea merkistö! Komennolla EditMsg pääsee editoimaan viestin statusbittejä. Se vastaa E:n painamista viestejä lukiessa. Bittieditorissa voi esimerkiksi tehdä viestistä yksityisen. Suositeltava tapa viestien lukemiseen on ReadNext. Se aloittaa vies- tien lukemisen ensimmäisestä lukemattomasta viestistä ja lukee ne loppuun kysei- sellä alueella. Toinen vaihtoehto on ReadAllNew, joka toimii muuten samoin, mut- ta lukee uudet viestit kaikilta alueilta. Vielä yksi komento on syytä ottaa puheeksi. ShowMessage näyttää käyttäjälle lyhyen tiedonannon. Sen perään tulostetaan prompti ja odotetaan näppäimenpainallusta. Tämä komento on tarkoitettu käytettäväksi noden laukaise- missa asynkronisissa ohjelmissa. Esimerkiksi TechQWK ja TechWWF käyttävät ShowMessagea kertoessaan, että viestipaketti on valmis. Funktioiden puolella tulee ensiksi kiinnostavista vastaan GetDateVal(). Se pa- lauttaa systeemikellon ajan päivien määränä tammikuun ensimmäisestä päivästä vuonna 1978. Tämä on TechnoBBS:n käyttämä päivämääräformaatti. Tiedostoalueista saa tietoa funktioilla GetLowMsg(), GetHighMsg() ja GetHighRead(). Funktiot pa- lauttavat alueen ensimmäisen ja viimeisen viestin numeron sekä viimeisen luetun viestin numeron. Näitä käytetään useissa seuraavassa luvussa esiteltävissä ko- mennoissa. Viestialueen ja alueryhmän numeron saa selville funktioilla GetMsgArea() ja Cur- rentSIG(), alueen nimen funktiolla GetMsgAreaName(). SIG:n nimen pitäisi selvitä funktiolla SIGName(), mutta se ei toimi. Ainakin minulla se palauttaa aina en- simmäisen SIG:n nimen, joten siitä ei ole hyötyä. Käyttäjän oikeudet jollekin alueelle voi tarkistaa funktiolla HasMsgAcc(). Se palauttaa arvon yksi, jos käyttäjällä on pääsy alueelle. Viimeinen tarkastelun kohteeksi pääsevä funktio on SIGtoReal(). Se muuttaa anne- tun viestialueen numeron todelliseksi aluenumeroksi. Sille annetaan alueen nume- ro SIG:n sisällä, ja se palauttaa numeron, joka vastaa msga.dat- sekä muiden tiedostojen numerointia. Jotkin komennot haluavat parametrinään alueen todelli- sen numeron. Tällainen on esimerkiksi MoveMsg. Tästä komennosta on lisää tietoa seuraavassa luvussa, jossa käsittelen lähemmin myös muutamia muita komentoja. {3Uusia komentoja {3--------------- Tässä viimeisessä luvussa julkaisen useita valmiita komentoja vapaasti portatta- vaksi mihin tahansa purkkiin. Jos käytät tässä julkaistua koodia, kerro kaikille siitä! Aloitamme omalla versiollani tiedostojenlistauskomennosta. Komento korvaa alku- peräisen list-komennon. Command "list" (N:1) { $sel = 0; $selmode = "-C"; $sort = "-SA"; $an = 1; $opt = ""; if(arg(1) != "") while(arg($an) != "") { $curr = upper(arg($an)); $temp = ""; if($curr == "ALL") { $temp = "-E"; $selmode = "-E"; }else if($curr == "SELECTOR") $sel = 1; else if($curr == "FILES") $temp = "-I"; else if($curr == "DAYS") { $an = $an + 1; $temp = "-D\(GetDateVal() - arg($an))" } else if($curr == "SINCE") { $an = $an + 1; $temp = "-D\(arg($an))" } else if($curr == "NEWFILES") { $temp = "-D\(GetUserMisc("LASTFSCAN"))"; SetUserMisc("LASTFSCAN",GetDateVal()); } else if($curr == "ALPHA") $sort = "-SA"; else if($curr == "REVERSE") $sort = "-SZ"; else if($curr == "CRON") $sort = "-SO"; else if($curr == "REVCRON") $sort = "-SN"; else if($curr == "FIND") { $an = $an + 1; $temp = "-F'\(arg($an))'" } else $temp = "-P\($curr)"; if($temp != "") $opt = "\($opt) \($temp)"; $an = $an + 1; } if($sel) { rexx("LogEntry Selecting"); rexx("SelectFiles \($selmode) -H \($opt) \($sort)"); } else { rexx("LogEntry Listing"); rexx("ListFiles -G -M -L \($opt) \($sort)"); }; }; Komennon formaatti on tällainen: LIST [ALL] [FILES] [NEWFILES] [DAYS ] [SINCE ] [SELECTOR] [ALPHA|REVERSE|CRON|REVCRON] [FIND ] [] Tämä on epäilemättä eräs tehokkaimmista ja monimutkaisimmista list-komennoista kautta purkkimaailman. Komennolla voi tehdä melkein mitä vain - listata tiedos- toja, etsiä niitä, imeä niitä, ottaa newfiles-listauksen jne. Tässä on selvitys komennon optioista: ALL Kaikki tiedostot kaikilta alueilta FILES Vain tiedostot (ALL-option kanssa myös ala-alueiden) NEWFILES Uudet tiedostot (viime filescanin jälkeen upitut) DAYS N päivää sitten ja sen jälkeen upitut tiedostot SINCE Sama juttu, toimii aivan kuin DOS-Listissä SELECTOR Käytä tiedostojen näyttämiseen FSE-valitsinta ALPHA Näytä tiedostot aakkosjärjestyksessä (oletus) REVERSE Käänteinen aakkosjärjestys CRON Aikajärjestys REVCRON Käänteinen aikajärjestys FIND Vain tiedostot, joissa on annettu merkkijono Pattern, johon täsmäävät tiedostot listataan FIND-option yhteydessä annettua merkkijonoa siis etsitään tiedoston nimestä ja lyhyestä kuvauksesta - ei tiedostosta itsestään. Merkkijonossa ei saa olla väli- lyöntejä. Optiolla ALL listataan kaikkien alueiden tiedostot aluekohtaisesti. FILES-optiolla listataan vain tiedostot, eikä ala-alueita näytetä ollenkaan. Yh- dessä käytettynä ALL- ja FILES-optiot saavat aikaan sen, että kaikkien alueiden tiedostot näytetään samassa listassa! Kokoruutuvalitsinta käytettäessä tähän riittää pelkkä ALL-optio - itse asiassa sen kanssa FILES-optiolla ei ole merki- tystä, koska SelectFiles ei tue I-optiota. Virheellisistä tai tunnistamattomista optioista ei huomauteta, vaan ne ohitetaan olankohautuksella. Parametrillisten optioiden parametrien olemassaoloa ei tar- kisteta, mutta en usko TechnoBBS:n siihen kaatuvan, vaikka jonkin option para- metri puuttuisikin, mutta tietysti käyttäjän tulee varmistua siitä, että kaikki parametrit annetaan. BBS:ni on "high-end", joten myös käyttäjien tulee olla asiantuntevia! Muutama esimerkki kertoo vähintään yhtä paljon kuin 1024 sanaa: list all files newfiles Näyttää uudet tiedostot samassa listassa (files-optio) kaikilta alueilta (all). Itse asiassa kaikki alueet ei ole absoluuttinen käsite. Kaikilla alueilla tar- koitetaan sitä aluetta, jolla käyttäjä on sekä sen ala-alueita. ListFiles- tai SelectFiles-komennolle (kumpaa käytetään, ratkeaa siitä, onko komentorivillä op- tio SELECTOR) annetaan all-option ollessa määritelty optio E. Optiota A ei tämä komento koskaan käytä. list days 30 cron Listaa viimeisen kuukauden aikana upitut tiedostot aikajärjestyksessä. list all find Tech Listaa kaikki tiedostot, joiden nimessä tai lyhyessä kuvauksessa esiintyy sa- nanpätkä "Tech". Tiedostoja etsitään kaikilta alueilta ja ne näytetään aluekoh- taisesti aakkosjärjestyksessä. list all files since 01-Jan-95 Voiko enää inhimillisemmäksi BBS:n käyttäminen mennä? Kyllä voi, mutta sen käsittely jääköön toistaiseksi. Tämä komento vastaa itse asiassa täysin DOSin List-komennon toimintaa. Komento luettelee kaikki tällä alueella ja sen ala- alueilla olevat tiedostot, jotka on upittu tänä vuonna. Olen kirjoittanut useimmat TechnoBBS:n komennot suurilta osin tai täysin uusik- si. Vakiona TechnoBBS:ään kuuluvat viestikomennot ovat aika sekavia. Jotkin käyttävät optioita, mutta ne ovat kryptisiä Unix-tyylisiä optioita. Tässä on esimerkiksi vielä viestivalikon list-komentoni: Command "list" (N:1) { $area = GetMsgArea(); if(GetHighRead($area) == GetHighMsg($area)) $defstart = GetLowMsg($area); else $defstart = GetHighRead($area) + 1; if(arg(1) == "") $start = AskInput("\(27)[32mFrom message? \(27)[0m", $defstart, 10, "NUMERIC"); else $start = arg(1); if(arg(2) == "") $end = AskInput("\(27)[32mTo message? \(27)[0m", GetHighMsg(GetMsgArea()), 10, "NUMERIC"); else $end = arg(2); rexx("SendModem \(13)\(10)"; rexx("ListMessages \($start) \($end)"); }; Komento ottaa parametreikseen listauksen alun ja lopun. Mikäli niitä ei määri- tellä, ne kysytään. Oletuksena tarjotaan promptissa aloitusviestiksi ensimmäistä lukematonta viestiä tai, jos alueen kaikki viestit on luettu, ensimmäistä ole- massa olevaa viestiä sekä lopetusviestiksi viimeistä viestiä. Samoja oletuksia tarjoaa myös esimerkiksi Find-komentoni. Lisäksi minulla on BBBS-tyylinen Mark Group -toiminto, joka käyttää samantapaista logiikkaa. En listaa enää muita va- kiokomentoja, mutta niiden lähdekoodia saa minulta pyytämällä. Siirtyminen viestivalikkoon ei ole niin helppoa kuin voisi luulla. Tässä on pa- ranneltu versio tehtävän tekevästä komennosta: Command "messages" (N:1) { rexx("LogEntry Messages"); rexx("SelectSIG \(arg(1))"); if(!CurrentSIG()) rexx("SelectSIG 1"); rexx("SendModem \(12)"); rexx("SelectSIGArea \(arg(2))"); menu("Msg.menu"); }; Tällä komennolla varmistutaan siitä, että päästään viestivalikkoon, vaikka ty- perä käyttäjä painaisikin vain enteriä tajuamatta valita SIG:iä tai viestialuet- ta. Alkuperäisellä koodillahan silloin silloin takaisin päävalikkoon, mutta tämä koodi vie silloin ensimmäisen SIG:n ensimmäiselle viestialueelle, joka yleensä on postialue, tai sinne, missä viimeksi on oltu. Viestit luetaan kätevästi painamalla enteriä, mutta miten olisi viestivalikkoon meneminen enterin painalluksella, kuten BBBS:ssä? Kas tässä: Command "" { $sig = GetUserMisc("DEFAULTSIG"); if($sig == "") $sig = 1; if(($sig < 1) | ($sig > 6) { rexx("SendModem\(27)[31mYour default SIG setting is invalid. \(13)\(10)\(13)\(10)\(27)[33m Using default default setting.\(13)\(10)"); $sig = 1; } rexx("LogEntry Messages"); rexx("SelectSIG \($sig)"); rexx("SelectSIGArea 1"); menu("Msg.menu"); }; Tämä tulee siis päävalikkoon, ja se vie käyttäjän viestivalikkoon painettaessa enteriä tyhjälle riville. Näin viestejä päästään lukemaan loginin jälkeen kah- della enterin painalluksella! Laita ennen kääntämistä ensimmäinen rexx()-funk- tiokutsu yhdelle riville. Komento vaatinee hieman selvitystä. Aikaisemmin minulla oli siinä vain "Select- SIG 1", mutta nyt SIG:n numero otetaan käyttäjämuuttujasta DEFAULTSIG. Tälle tu- li tarvetta, koska haluan yleensä päästä suoraan SakuNet-alueille, mikä näin on- nistuu helposti laittamatta sitä kiinteäksi oletusarvoksi. Riittää, että DE- FAULTSIG:n arvo on 3, joka on SakuNetin SIG:n numero. Tämän asettamiseksi tarvi- taan tietysti sopiva koodi esimerkiksi terminaaliasetusvalikkoon: des = GetUserMisc(ln, "DEFAULTSIG") select when des = 1 then des = "Yleiset alueet" when des = 3 then des = "SakuNet" when des = 6 then des = "WebNet" otherwise des = "" end SendModem "12 "||CYAN||"Default SIG: "||WHITE||des||CRLF Tämä koodi tulee sinne alkuun, missä tulostetaan olemassaolevat valinnat. Seu- raava koodi tulee loppuun. Itselläni valinnan numero on 12, mutta siihen voi laittaa muutakin, mikä sitten sattuu olemaan ensimmäinen vapaa numero. SIG-koo- deistani on tarkoituksella poistettu SIG:t 2, 4 ja 5. when cmdid = "12" then do dez = GetUserMisc(ln, "DEFAULTSIG") if dez = "" then dez = 1 des = "" do while des = "" SendModem CRLF SendASCII "Text/DefaultSIG.txt" des = AskInput(ln, "Select: ", "", 2, "NUMERIC"); select when des = "1" then nop when des = "2" then nop when des = "3" then nop when des = "4" then nop when des = "5" then nop when des = "6" then nop when des = "" then des = dez otherwise des = "" end end Koodi toimii vastaavasti kuin esimerkiksi offlinerin valinta (tulee myöhemmin). Ovelalla manipuloinnilla saadaan käyttäjä valitsemaan oletusarvo hänen tietämättään. Tosin myös koodi, joka arvoa käyttää, osaa valita oletusoletuksen, jos arvo on väärä tai puuttuu. Ohjeeksi lähetetään tiedosto DefaultSIG.txt: The following SIGs are available in this system: 1 Yleiset alueet 3 SakuNet 6 WebNet Select one or press enter for no change or default selection. Ensimmäinen varsinainen uusi komento tulee tässä. Komennolla voi siirtää viestin toiselle alueelle: Command "move" (N:2) { if(GetUserAccess() < 1000) { rexx("SendModem \(27)[31mNo access\(13)\(10)"); rexx("LogEntry Attempted to move a message"); break; } $msg = arg(1); $area = arg(2); if($msg == "") { $msg = AskInput("Message number: ", "", 10, "NUMERIC"); } if($area == "") { $area = AskInput("Destination area: ", "", 10, "NUMERIC"); rexx("SendModem \(13)\(10)\(13)\(10)"); } if($msg != "") if($area != "") { rexx("SendModem Moving message.\(13)\(10)"); rexx("MoveMsg \($msg) \($area)"); rexx("KillMsg \($msg)"); rexx("LogEntry Moved message \($msg) from \(GetMsgAreaName( GetMsgArea())) to \(GetMsgAreaName($area)) as #\( GetHighMsg($area))\(13)\(10)"); } }; En tiedä, miten TechMenuComp suhtautuu tuolla tavalla jaettuihin riveihin, mutta uskoisin, että viimeinen rexx()-funktiokutsu kannattaa palauttaa yhdelle rivil- le. Jaoin sen kahdelle riville taittoteknisistä syistä... Komento varmistaa, että käyttäjällä on SysOp-oikeudet (oikeustaso => 1000). Komennolle annetaan siirrettävän viestin numero ja alue, jolle viesti siirretään. Jos niitä ei anne- ta komentorivillä, ne kysytään erikseen. Kohdealueen numero on tässä nyt alueen todellinen numero sellaisena kuin se msg.dat-tiedoston nimessä on. Tätä voisi helpottaa käyttämällä SIGto- Real()-funktiota, mutta ainakin minulla on tarve siirtää joskus viestejä SIG:stä toiseen, joten tuollaista ei voi käyttää, koska siirto estyisi. Viestin siirtämisen jälkeen alkuperäinen tuhotaan, koska MoveMsg ei tee sitä itse. Itse asiassa se onkin toiminnaltaan pikemminkin CopyMsg... Jälleen tehdään perusteellinen lokimerkintä... Alkuperäisen viestin numerolla ei tosin enää ole merkitystä, eikä uudenkaan viestin numero välttämättä ole aina oikein, mutta mahdollisuus sen väärin menemiseen on hyvin pieni (jos jokin toi- nen node ehtii välissä laittaa kohdealueelle toisen viestin). TechnoBBS:ssä ei ole minkäänlaista viestialueiden läpikäyntikomentoa. Esimerkik- si BBBS:ssä on hyvä show (conference status) -komento. Olen kirjoittanut vastaa- van TechnoBBS:lle. Se ei ole yhtä monipuolinen, koska olisi käytännössä äärettömän hidasta ja miltei mahdotonta skannata lennossa, kuinka monta viestiä nimenomaiselle käyttäjälle on. Senhän saa selville mc-komennolla, joten alueilla olevien viestien kokonäismäärä ja uusien viestien määrä riittänevät: Command "scan" (N:2) { $curr = GetMsgArea(); $signum = arg(1); $mode = arg(2); if($signum == "") $signum = CurrentSIG(); if(upper(arg(1)) == "NEW") { $signum = CurrentSIG(); $mode = "NEW"; } if($signum == 6) { $sig = "WebNet"; $area = 101; $max = 130; } else if($signum == 3) { $sig = "SakuNet"; $area = 31; if(GetUserAccess() > 999) $max = 64; else $max = 61; } else { $sig = "Yleiset alueet"; $area = 3; $max = 12; } rexx("SendModem \(27)[32mScanning message areas: \( $sig)\(13)\(10)\(13)\(10)"); rexx("LogEntry Scanning SIG \($sig)"); $hightotal = 0; $msgtotal = 0; while($area <= $max) { $highread = GetHighRead($area); $highmsg = GetHighMsg($area); $areaname = "\(GetMsgAreaName($area)):"; if($highmsg) { $doarea = 1; if($mode != "") if($highmsg == $highread) $doarea = 0; if(!(GetAreaMode($area))) $doarea = 0; if($doarea) { rexx("SendModem \(27)[33m\(str($areaname,1,24))\(27)[0m\( $highmsg - $highread) unread messages (\( $highmsg) total, last read \($highread))\(13)\(10)"); $hightotal = $hightotal + $highread; $msgtotal = $msgtotal + $highmsg; } } $area = $area + 1; } rexx("SendModem \(13)\(10)\(27)[33m\(str("Total:",1,24))\(27)[0m\( $msgtotal - $hightotal) unread messages (\( $msgtotal) total, read \($hightotal))\(13)\(10)"); }; Komento käy läpi halutun SIG:n alueet ja kertoo niistä tietoa. Tässä on esimerk- kituloste: AZ.Muut: 4 unread messages (61 total, last read 57) AZ.Net: 43 unread messages (282 total, last read 239) AZ.SciFi & Trek: 2 unread messages (22 total, last read 20) AZ.Tee-se-itse: 0 unread messages (26 total, last read 26) AZ.TV & Elokuvat: 0 unread messages (50 total, last read 50) AZ.SAKU/Yleinen: 5 unread messages (28 total, last read 23) AZ.SAKU/Info: 0 unread messages (21 total, last read 21) AZ.SAKU/Ohjelmointi: 0 unread messages (6 total, last read 6) AZ.SAKU/Toimitus: 0 unread messages (3 total, last read 3) AZ.SAKU/Yhdistys: 0 unread messages (6 total, last read 6) Total: 54 unread messages (505 total, read 451) Komento ottaa parametrinään skannattavan SIG:n numeron. Jos sitä ei anneta, skannataan se SIG, jossa ollaan. Lisäksi komento tunnistaa option NEW, joka määrittelemällä näytetään vain alueet, joilla on uusia viestejä. Option NEW voi antaa myös ilman SIG-numeroa. Koodissa on hieman logiikkaa. Vain alueet, joille käyttäjä on liittynyt ja joilla on viestejä, näytetään. SIG:n nimet on asetettava käsin, koska SIGName ei toimi. Lisäksi SIG-numeron vertailussa asetetaan asianmukaiset aluerajat. Huomaa SakuNetin tapauksessa vaihtoehtoinen loppumisalue. Lopussa on kolme aluetta, jotka eivät näy lop- pukäyttäjille, joten niitä ei myöskään näytetä skannatessa kuin SysOpille. Janne Siren on tehnyt myös viestialuestatistiikkaa kertovan ohjelman. Olen vi- rittänyt myös siihen SIG-pohjaisen toiminnan: Command "mstats" (N:2 G:) { $signum = arg(1); if($signum == "") $signum = CurrentSIG(); if($signum == 6) { $sig = "WebNet"; $area = 101; $max = 130; } else if($signum == 3) { $sig = "SakuNet"; $area = 31; if(GetMask()&1024) $max = 64; else $max = 61; } else { $sig = "Yleiset alueet"; $area = 3; $max = 12; } rexx("SendModem \(27)[32mViewing message area statistics for SIG \( $sig)\(13)\(10)\(13)\(10)"); dos("RX >NIL: BBS:Rexx/MessageStats.rexx \(node()) \($area) \($max)"); rexx("LogEntry Examining message area statistics for SIG \($sig)"); }; Tämä vaatii muutoksia myös itse ohjelmaan. Poista MessageStats.rexxistä FirstA- rea- ja LastArea-määrittelyt ja korvaa ln-määrittely komennolla: Parse ARG ln FirstArea LastArea . Älä unohda lopussa olevaa pistettä. Näillä muutoksilla myös MessageStats käy läpi vain yhden SIG:n, mikä on järkevin toimintatapa. TechnoBBS:n mukana ei tule myöskään tarpeellista SIG:n vaihtokomentoa. Seuraa- valla koodinpätkällä homma hoituu: Command "sig" (N:1) { $sig = arg(1); $area = arg(2); if(($sig != "") && ($area == "")) $area = 1; rexx("SelectSIG \($sig)"); if($area == "") { rexx("SendModem \(12)"); rexx("SelectSIGArea"); } else rexx("SelectSIGArea \($area)"); rexx("LogEntry Message area: \(GetMsgAreaName(GetMsgArea()))"); }; Komennolle annetaan halutun SIG:n numero sekä viestialueen numero siellä. Jos näitä ei anneta, ne kysytään. Poikkeustapaus on kuitenkin se, että SIG:n numero on määritelty, mutta alueen ei. Silloin siirrytään suoraan alueelle yksi. Tämä on helppo tapa vaihtaa nopeasti alueryhmää. Joskus verkosta tulee viestejä, joiden merkistö on väärä. Näitä voi tulla myös käyttäjien replypaketeissa. Viestin voi kuitenkin merkistökonvertoida sen olles- sa jo viestikannassa: Command "convert" (N:4) { if(GetUserAccess() < 1000) { rexx("SendModem \(27)[31mNo access\(13)\(10)"); rexx("LogEntry Attempted to convert a message"); break; } $msg = arg(1); $set = arg(2); if($msg == "") $msg = AskInput( "\(27)[32mConvert which message: \(27)[0m", "", 10, "NUMERIC"); if($set == "") $set = AskInput( "\(27)[32mFrom which charset? \(27)[0m", "", 10); if($msg != "") if($set != "") { rexx("SendModem \(27)[32m\(13)\(10)Converting message \( $msg) from \($set) to ISO\(13)\(10)"); rexx("LogEntry Converting message \($msg) from \($set) to ISO"); rexx("ConvertMsg \($msg) \($set)"); } else rexx("SendModem \(27)[31m\(13)\(10)Required argument missing"); } Komento ottaa parametrikseen konvertoitavan viestin numeron sekä merkistön, jos- ta konvertoidaan. Yleensä lähdemerkistö on SF7 tai IBM. SF7:ssä olevassa vies- tissä näkyvät esimerkiksi pienet ä-kirjaimet avautuvina kaarisulkuina ja pienet ö-kirjaimet pystyviivoina. IBM-merkistöllä esimerkiksi ä-kirjaimesta tulee kur- sorin siirto alaspäin - TechnoBBS:llä IBM-merkistön havainnee niin, että skandi- merkit puuttuvat kokonaan. BBBS:ssä on myös Duplicate-komento, jolla voidaan jo kirjoitettu viesti ottaa uudelleen editoitavaksi. TechnoBBS tarjoaa komennon ReWriteMsg, jota käyttämällä vastaavan komennon toteuttaminen siihen on erittäin helppoa: Command "duplicate" (N:3) { $msg = arg(1); if($msg == "") $msg = AskInput( "\(27)[32mDuplicate which message: \(27)[0m", "", 10, "NUMERIC"); if($msg != "") { rexx("LogEntry Duplicating message \($msg) as \(GetHighMsg( GetMsgArea()) + 1)"); rexx("ReWriteMsg \($msg)"); } else rexx( "SendModem \(27)[31m\(13)\(10)Operation cancelled\(13)\(10)"); } Komennolle annetaan parametrinä dupattavan viestin numero. Viestin voi dupata vain sen kirjoittaja tai käyttäjä, jolla on viestineditointi- tai SysOp-oikeu- det. Laitan tähän nyt niitä lupaamiani Extras-valikon ylimääräisiä komentoja. Komen- not ajavat jonkin järjestelmäkomennon ja kertovat käyttäjälle näin tietoa sys- teemistä. Command "processes" (N:2) { rexx("LogEntry Listing processes"); if(upper(arg(1)) == "ALL") dos("PS >T:pr_\(node())"); else dos("Status >T:pr_\(node())"); rexx("SendASCII T:pr_\(node())"); dos("Delete T:pr_\(node())"); }; Tämä ohjelma lähettää käyttäjälle prosessilistan. Vaihtoehtoisesti käytetään Status-komentoa tai perusteellisempaa PS-komentoa, joka listaa myös Exec- tehtävät. Tulostus voi olla pitkä, joten se ohjataan temppitiedostoon, joka lähetetään SendASCII-komennolla. Näin listaukseen saadaan tarpeen vaatiessa Mo- re-prompti. Käytän temppitiedoston nimessä nodenumeroa, koska on olemassa mah- dollisuus, että komento ajettaisiin yhtä aikaa useammalla kuin yhdellä nodella. Command "memory" (N:3) { rexx("LogEntry Checking available memory"); dos("Avail >TECHIO:\(node())"); }; Yksinkertainen komento MEM kertoo koneen muistitilanteen. Command "time" (N:3 G:) { rexx("LogEntry Acquiring current time"); dos("Date >TECHIO:\(node())"); }; Yhtä yksinkertainen TIM-komento kertoo oikean ajan. Command "ds" { rexx("LogEntry Checking disk space"); if(upper(arg(1)) == "FULL") dos("Inf >T:ds_\(node())"); else dos("Info >T:ds_\(node())"); rexx("SendASCII T:ds_\(node())"); dos("Delete T:ds_\(node())"); }; Komento DS toimii samalla tavalla kuin PR. Se kertoo massamuistien tilan. Sitä tarkoitusta varten ajetaan Info-komento. Vaihtoehtoisesti ajetaan Inf-komento, joka kertoo enemmän tietoa kuin vakiokomento Info. Tiedot tulostetaan samaan ta- paan kuin prosessilistakin - Inf tulostaa myös muistitilanteen ja Volume-luette- lon, joten senkin tuloste voi olla yli sivun mittainen. Tähän mennessä pitäisi jo olla selvää, että ihannoin BBBS:ää! No, ei aivan sentään, mutta BBBS:ssä on monia hyviä ominaisuuksia, ja niitä olen tuonut Tech- noBBS:ään useita. Yksi näistä on tuki bulletiineille eli "tekstikirjastolle". Bulletiinit ovat tekstitiedostoja, joita voi lukea linjalla ja imeä yksitellen: Command "bulletins" (N:4 G:) { rexx("LogEntry Bulletins"); $read = arg(1); $bull = "?"; while ($bull != "") { if($read == "") $bull = AskInput( "\(27)[32mBulletin number (? for list): \(27)[0m","",40); else { $bull = $read; $read = ""; } if($bull == "?") $bull = "list"; if(str($bull,1,1) == "q") $bull = ""; else if($bull == "d") { rexx("ClearFiles"); while($bull != "") { rexx("SendModem \(13)\(10)"); $bull = AskInput( "\(27)[32mBulletin number to download: \(27)[0m","",40); if($bull != "") { rexx("LogEntry Downloading bulletin \($bull)"); rexx("AddFile BBS:Bulletins/\($bull)"); } }; $dl = 1; while($dl) { if(SendFiles()) $dl = 0; else { rexx("SendModem \(13)\(10)\(13)\(10)\(13)\(10)\(27)[31m Transfer failed.\(13)\(10)\(13)\(10)"); if(!(GetYesNo("\(27)[36mSend again? \(27)[0m",1,1))) $dl = 0; } } } else if($bull != "") { rexx("LogEntry Reading bulletin \($bull)"); rexx("SendModem \(12)"); rexx("SendASCII BBS:Bulletins/\($bull)"); rexx("SendModem \(13)\(10)"); }; } }; Tämä komento käyttää hyväkseen ClearFiles/AddFile/SendFiles()-toimintosarjaa. Olen ajatellut kirjoittaa koodin uusiksi Rexx-kielellä, koska nykyisellään bul- letiineja ei voi konvertoida. Online-Saku-lukijassani olisi valmis konvertointi- koodi sekä pakkaus. Ne voisi helposti kopioida myös bulletiinitukeen, jolloin myös imettävät bulletiinit voisi konvertoida oikealle merkistölle sekä pakata ennen siirtoa. Komennolle voi antaa parametriksi bulletiinin numeron, joka luetaan välittömästi, minkä jälkeen tulostetaan prompti. Muuten sen saa heti. Paramet- riksi voi antaa bulletiinin numeron tai komentoja. Tällä hetkellä hyväksyttäviä komentoja ovat d, jolla voi imeä bulletiineja, tai ?, joka tulostaa bulletiini- listan, sekä q, jolla pääsee takaisin päävalikkoon, mikä onnistuu myös enterin painamisella tyhjälle riville. Bulletiinit pidetään hakemistossa BBS:Bulletins. Bulletiinilista on tiedostossa BBS:Bulletins/list. Kun promptiin vastataan kysymysmerkillä, se muutetaan "list":ksi, jolloin lähetetään tuo lista. Käyttäjälle lähetetään suoraan sen ni- minen tiedosto, minkä hän on promptiin kirjoittanut. Lisäksi siitä tehdään loki- merkintä. Listan tapauksessa tiedoston nimi on "list", joten lokientryksi tulee näppärästi Reading bulletin list... Tässä on esimerkiksi oma bulletiinilistani: Bulletin List 0 Tietoa bulletiineista 1 Uploading Instructions 2 Star Fleet Top Callers 3 Star Fleet Top Message Writers 4 Star Fleet Top Uploaders 5 Star Fleet Top Downloaders Alaryhmät 20 SAKU 30 Amigan tulevaisuus 40 Amigan laitteisto ja ohjelmisto 60 Tee-se-itse 80 Sekalaiset 100 Star Trek enter to quit bulletins, cls to clear screen, d to download Lopussa oleva ylimääräinen ohje on syytä pitää mukana, koska noita tietoja ei anneta muualla. Bulletiinit ovat hyvä paikka pitää TopTechin tuottamat top-lis- tat. Niiden generointi lennossa on hidasta ja epäsuotavaa, koska TopTech voi se- koittaa TechConin toimintaa. Alaryhmien tekeminen on myös mahdollista. Bulletii- nien ei ole pakko olla numeroituja. Niiden nimenä voi olla myös jokin sana, mut- ta sanavälejä siinä ei ole suotavaa olla. Alaryhmien hakemistot pidetään vastaavissa tiedostoissa, esimerkiksi SAKU-bulle- tinien hakemisto on tiedostossa BBS:Bulletins/20: Bulletin List - SAKU 21 Amiga-käyttäjät perustavat rekisteröidyn yhdistyksen 22 Uusi HTML-formaatin SAKU-lukija, ideoijana Jukka Aho (30k) 23 SAKU-kokous IRC:ssä 24 Seuraavan Sakun uusin tilannekatsaus 25 Esa Heikkinen perustaa SAKU-netin! 26 SAKU-netin nodelista enter to quit bulletins, cls to clear screen, d to download Ohje on aina syytä laittaa kaikkiin hakemistoihin. Varsinaiset bulletiinit ovat tietysti tiedostonimiltään BBS:Bulletins/21, BBS:Bulletins/22 ja niin edelleen. Olen nähnyt vastaavan palvelun ainakin Amiga Zonessa, ja se on minun mielestäni erittäin kätevä tapa välittää tekstimuotoista tietoa. Nykyään eräs tärkeimmistä BBS:n palveluista on viestien etäluvun järjestäminen. TechnoBBS tukee QWK:ta ja WWF:ää ja tarjoaa hyvät mahdollisuudet etälukuun, mut- ta BA/BW/QU/WU-sarja on aivan liian mahdoton muistettavaksi. Näin ollen olen kirjoittanut kaksi komentoa, grab ja put. Grabilla otetaan viestit ja putilla laitetaan vastaukset. Komennot sisältävät alkuperäisten komentojen koodin, mutta ne on ympäröity uudenlaisella liittymällä: Command "grab" (G:) { $format = GetUserMisc("OFFLINER"); if($format == "") { rexx("SendModem \(13)\(10)\(27)[31mThere is no Offliner defined. \(13)\(10)\(13)\(10)"); $resp = GetYesNo("\(27)[0mDo you want to use WWF (or QWK) ", 1, 1); if($resp == 1) $format = "WWF"; else $format = "QWK"; SetUserMisc("OFFLINER",$format); } if(lower(arg(1)) == "a") SetUserMisc("OFFLINERMODE", 1); else if(lower(arg(1)) == "s") SetUserMisc("OFFLINERMODE", 0); if(GetUserMisc("OFFLINERMODE") == "") { rexx("SendModem \(13)\(10)\(27) [31mYou have no offliner operation mode defined. Using default. \(13)\(10)\(13)\(10)"); SetUserMisc("OFFLINERMODE",0); SetUserMisc("WWFASYNC",0); SetUserMisc("QWKASYNC",0); } if($format == "QWK") { rexx("SendModem \(13)\(10)\(27)[33mPacking QWK messages"); if(GetUserMisc("OFFLINERMODE")) { rexx("LogEntry Packing QWK messages asynchronously"); rexx("SendModem asynchronously, you will be informed on completion\(13)\(10).\(13)\(10)\(27)[0m"); dos("Run <>NIL: TechQWK \(node()) O BBS:Cfg/TechQWK.Cfg"); } else { rexx("LogEntry Packing QWK messages"); rexx("SendModem .\(13)\(10)\(13)\(10)"); dos("TechQWK \(node()) O BBS:Cfg/TechQWK.Cfg"); rexx("SendModem \(13)\(10)"); GetHotkey("\(27)[32mPress any key to start sending...\(27)[0m"); rexx("SendModem \(13)\(10)\(13)\(10)"); rexx("MarkAnyFile \(GetUserPath(GetUserName()))/SFL.QWK"); rexx("Download MARKEDONLY"); } } else { rexx("SendModem \(13)\(10)\(27)[33mPacking WWF messages"); if(GetUserMisc("OFFLINERMODE")) { rexx("LogEntry Packing WWF messages asynchronously"); rexx("SendModem asynchronously, you will be informed on completion\(13)\(10)\(13)\(10)\(27)[0m"); dos("Run <>NIL: TechWWF \(node()) O BBS:Cfg/TechWWF.Cfg"); } else { rexx("LogEntry Packing WWF messages"); rexx("SendModem .\(13)\(10)\(13)\(10)"); dos("TechWWF \(node()) O BBS:Cfg/TechWWF.Cfg"); rexx("SendModem \(13)\(10)"); GetHotkey("\(27)[32mPress any key to start sending...\(27)[0m"); rexx("SendModem \(13)\(10)\(13)\(10)"); rexx("MarkAnyFile \(GetUserPath(GetUserName()))/SFL.WWF"); rexx("Download MARKEDONLY"); } } }; Command "put" (G:) { $format = GetUserMisc("OFFLINER"); if($format == "") { rexx("SendModem \(13)\(10)\(27)[31mThere is no Offliner defined. \(13)\(10)\(13)\(10)"); $resp = GetYesNo("\(27)[0mAre you using WWF (or QWK) ", 1, 1); if($resp == 1) $format = "WWF"; else $format = "QWK"; SetUserMisc("OFFLINER",$format); } if(lower(arg(1)) == "a") SetUserMisc("OFFLINERMODE", 1); else if(lower(arg(1)) == "s") SetUserMisc("OFFLINERMODE", 0); if(GetUserMisc("OFFLINERMODE") == "") { rexx("SendModem \(13)\(10)\(27)[31mYou have no offliner operation mode defined. Using default.\(13)\(10)\(13)\(10)"); SetUserMisc("OFFLINERMODE",0); SetUserMisc("WWFASYNC",0); SetUserMisc("QWKASYNC",0); } if($format == "QWK") { rexx("SendModem \(13)\(10)\(27)[33mUploading QWK messages. \(13)\(10)\(13)\(10)"); rexx("LogEntry Uploading QWK messages"); dos("Delete \(GetUserPath(GetUserName()))/SFL.REP quiet"); rexx("SendModem \(13)\(10)\(27)[33mStart sending SFL.REP \(13)\(10)\(13)\(10)\(27)[0m"); if(ReceiveFiles()) { if(GetUserMisc("OFFLINERMODE")) { dos("Run >NIL: TechQWK \(node()) I BBS:Cfg/TechQWK.Cfg"); rexx("SendModem \(27)[33mProcessing replies asynchronously \(13)\(10)\(27)[0m"); } else dos("TechQWK \(node()) I BBS:Cfg/TechQWK.Cfg"); } else rexx("SendModem \(13)\(10)\(13)\(10)\(27)[31mTransfer failed \(13)\(10)\(27)[0m"); } else { rexx("SendModem \(13)\(10)\(27)[33mUploading WWF messages. \(13)\(10)\(13)\(10)"); rexx("LogEntry Uploading WWF messages"); dos("delete \(GetUserPath(GetUserName()))/SFL.RRF quiet"); rexx("\(13)\(10)\(27)[33mStart sending SFL.RRF \(13)\(10)\(13)\(10)\(27)[0m"); if(ReceiveFiles()) { if(GetUserMisc("OFFLINERMODE")) { dos("Run >NIL: TechWWF \(node()) I BBS:Cfg/TechWWF.Cfg"); rexx("SendModem \(27)[33mProcessing replies asynchronously \(13)\(10)\(27)[0m"); } else dos("TechWWF \(node()) I BBS:Cfg/TechWWF.Cfg"); } else rexx("SendModem \(13)\(10)\(13)\(10)\(27)[31mTransfer failed \(13)\(10)\(27)[0m"); } }; Tämä koodi meni varsin risaiseksi, koska jouduin jakamaan useita rivejä. Jos ha- luat hyödyntää sitä, parasta lienee pyytää minulta suoraan kunnollinen versio. Komennoissa on paljon päällekkäistä koodia, joten tuota voisi parannella vielä. Ideana on, että viestien ottaminen ja laittaminen onnistuu samalla komennolla riippumatta siitä, mitä formaattia käyttää. Käytän käyttäjämuuttujaa OFFLINER välittämään tiedon siitä, missä formaatissa viestien tulee olla. Vaihtoehdot ovat tuetut QWK ja WWF. Toinen käyttämäni muuttuja on OFFLINERMODE. Se sisältää periaatteessa tiedon siitä, pakataanko linjalla vaiko taustalla. Lisäksi sen avulla voidaan aktivoida automaattipakkaus loginissa. Lisäksi täytyy kuitenkin asettaa myös QWKASYNC ja WWFASYNC, jotta bundlerit osaavat toimia oikein. OFFLINERMODE voi olla 0, 1, 2 tai 3. Nolla tarkoittaa pakkausta linjalla ja yksi taustalla. Myös arvoilla kaksi ja kolme pakataan taustalla. Lisäksi arvo kaksi aktivoi automaattipakkauksen. Arvo kolme toimii muuten samoin, mutta asia var- mistetaan vielä käyttäjältä. Näitä toimintoja varten tarvitaan koodia Login.rex- xiin: Mode = GetUserMisc(ln,"OFFLINERMODE") if Mode = 3 then do Mode = Mode - GetYesNo(ln, "Engage bundler? ", 1, 1) SendModem CRLF end if Mode = 2 then do Format = GetUserMisc(ln,"OFFLINER") if Format = "WWF" then address command 'Run >NIL: TechWWF '||ln ||' O BBS:Cfg/TechWWF.Cfg' if Format = "QWK" then address command 'Run >NIL: TechQWK '||ln ||' O BBS:Cfg/TechQWK.Cfg' LogEntry "Autopacking messages using "||Format end Tämä koodi tulee Login.rexxin loppuun juuri ennen LoginExtra.rexxin ajamista. Koodi ajaa bundlerin, jos OFFLINERMODE on kaksi. Jos se on kolme, asiaa kysytään käyttäjältä. Koodi vähentää ovelasti vastauksen moodiarvosta, jolloin siitä tu- lee kaksi, kun käyttäjä vastaa myöntävästi, eli pakkaus käynnistetään! Ja lokiin tehdään tietysti merkintä unohtamatta mainita, mitä formaattia käytetään... Olen koonnut etälukuvalinnat omaan asetusvalikkoonsa, Offliner settings. Koodi on tiedostossa BBS:Rexx/OS.rexx. En laita tähän koko koodia, koska sen pituus on yhdeksän kilotavua, mutta otan mukaan olennaisimman. Suurin osa koodista onkin samaa, joka on jo vakiona. Tässä tulee kuitenkin koodi, jolla valitaan offline- rin moodi, paketin formaatti ja merkistökonversio. Mikään näistä ei ole Tech- noBBS:n vakiotoiminto. Ensiksi tulee aina asetusohjelman alkuun tuleva asetuksen näyttävä koodi ja sitten sen muuttamiseen tarvittava koodi. des = GetUserMisc(ln, "OFFLINERMODE") select when des = 0 then des = "Online" when des = 1 then des = "Background" when des = 2 then des = "Auto" when des = 3 then des = "Query" otherwise des = "" end SendModem CRLF||PURPLE||"Offliner Settings"||CRLF||CRLF||WHITE SendModem "1 "||CYAN||"Packing mode ........................ "|| WHITE||des||CRLF when cmdid = "1" then do des = GetUserMisc(ln, "OFFLINERMODE") select when des = 0 then des = 1 when des = 1 then des = 2 when des = 2 then des = 3 when des = 3 then des = 0 otherwise des = 0 end call SetUserMisc ln, "OFFLINERMODE", des if des = 0 then do call SetUserMisc ln, "WWFASYNC", 0 call SetUserMisc ln, "QWKASYNC", 0 call SetUserMisc ln, "WWFTASKPRI", 0 call SetUserMisc ln, "QWKTASKPRI", 0 end else do call SetUserMisc ln, "WWFASYNC", 1 call SetUserMisc ln, "QWKASYNC", 1 call SetUserMisc ln, "WWFTASKPRI", -1 call SetUserMisc ln, "QWKTASKPRI", -1 end end des = GetUserMisc(ln, "OFFLINER") if des = "" then des = "" SendModem "2 "||CYAN||"Bundle format ....................... "|| WHITE||des||CRLF when cmdid = "2" then do dez = GetUserMisc(ln, "OFFLINER") if dez = "" then dez = "WWF" des = "" do while des = "" SendModem CRLF SendASCII "Text/Offliner.txt" des = AskInput(ln, "Select: ", "", 2, "NUMERIC"); select when des = "1" then des = "WWF" when des = "2" then des = "QWK" when des = "" then des = dez otherwise des = "" end end call SetUserMisc ln, "OFFLINER", des end des = GetUserMisc(ln, "QWKOUTCSET") select when des = "BBS:CharSet/ISO" then des = "ISO" when des = "BBS:CharSet/IBM" then des = "IBM" when des = "BBS:CharSet/SF7" then des = "SF7" when des = "BBS:CharSet/ISOSF7" then des = "ISOSF7" when des = "BBS:CharSet/IBMSF7" then des = "IBMSF7" otherwise des = "" end SendModem "8 "||CYAN||"QWK Outbound Character Conversion ... " ||WHITE||des||CRLF when cmdid = "8" then do CSet = "" do while CSet = "" SendASCII "Text/CharSet.txt" CSet = AskInput(ln, "Select: ", "0", 2, "NUMERIC") select when CSet = "0" then CSet = "BBS:CharSet/ISO" when CSet = "1" then CSet = "BBS:CharSet/IBM" when CSet = "2" then CSet = "BBS:CharSet/SF7" when CSet = "3" then CSet = "BBS:CharSet/ISOSF7" when CSet = "4" then CSet = "BBS:CharSet/IBMSF7" otherwise CSet = "0" end end call SetUserMisc ln, "QWKOUTCSET", CSet SendModem CRLF end Tässä vielä esimerkiksi Offliner.txt, joka lähetetään formaattia valittaessa: The following offliners are available in this system: 1 WWF 2 QWK Select one or press enter for no change or default selection.